package com.ahmedmisbah.andgenplus.restgenerator.generator;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;

import com.ahmedmisbah.andgenplus.codegenerator.CodeGenerator;
import com.ahmedmisbah.andgenplus.codegenerator.beans.GeneratorTemplate;
import com.ahmedmisbah.andgenplus.codegenerator.exceptions.CodeGeneratorException;
import com.ahmedmisbah.andgenplus.constants.GlobalConstants;
import com.ahmedmisbah.andgenplus.restgenerator.beans.Mapper;
import com.ahmedmisbah.andgenplus.restgenerator.beans.Parameter;
import com.ahmedmisbah.andgenplus.restgenerator.beans.Parameter.ParameterType;
import com.ahmedmisbah.andgenplus.restgenerator.beans.RESTWebServiceDescriptor;
import com.ahmedmisbah.andgenplus.restgenerator.beans.RESTWebServiceDescriptor.URLFormat;
import com.ahmedmisbah.andgenplus.utils.android.AndroidManifestUtil;
import com.ahmedmisbah.andgenplus.utils.android.AndroidUtil;
import com.ahmedmisbah.andgenplus.utils.file.FileUtil;
import com.ahmedmisbah.andgenplus.utils.file.exceptions.FileUtilException;
import com.ahmedmisbah.andgenplus.utils.json.exceptions.Json2PojoGeneratorException;
import com.ahmedmisbah.andgenplus.utils.json.generator.Json2PojoGenerator;
import com.ahmedmisbah.andgenplus.utils.pde.PDEUtil;
import com.ahmedmisbah.andgenplus.utils.velocity.VelocityTemplate;
import com.ahmedmisbah.andgenplus.utils.velocity.VelocityUtil;

public class RESTCodeGenerator extends CodeGenerator {

	private String pojoClassPath;
	private RESTWebServiceDescriptor restDescriptor;
	private final String EXCEPTION_TEMPLATE = "exception";
	private final String REST_WS_GET_CLIENT_TEMPLATE = "restGetClient";
	private final String SERVICE_TEMPLATE = "service";
	private final String ACTIVITY_TEMPLATE = "activity";

	public RESTWebServiceDescriptor getRestDescriptor() {
		return restDescriptor;
	}

	private String jsonPojoName;
	private String restWSClientClassPath;
	private String exceptionClassPath;
	private String serviceClassPath;

	private void generateJsonPojos() throws CodeGeneratorException {

		System.out.println(PDEUtil.getSrcFolderPath());

		// String
		// srcFolderPath="D:\\Ahmed\\Employers\\MiG\\Code\\TestProject"+File.separator+"src"+File.separator;
		String jsonString = restDescriptor.getJson();
		String webServiceName = restDescriptor.getWebServiceName();
		String packageName = restDescriptor.getPackagePath() + ".pojos";

		// Create temp file for JSON
		String tempFilePath = GlobalConstants.APPLICATION_ROOT_DIR
				+ File.separator + "temp" + File.separator
				+ System.currentTimeMillis() + ".json";

		System.out.println(tempFilePath);
		try {
			FileUtil.writeFileAsUTF8(jsonString, tempFilePath);

			jsonPojoName = webServiceName + "Pojo";

			// Generate Pojos
			Json2PojoGenerator.generatePojoFromJson(tempFilePath,
					PDEUtil.getSrcFolderPath(), jsonPojoName, packageName);

			pojoClassPath = PDEUtil.getClassPath(packageName, jsonPojoName);

			// Add pojo package path to be used in loading thier class names in
			// the UI
			GlobalConstants.globalCache.put(GlobalConstants.POJO_PACKAGE_PATH,
					packageName);

			// Add Jackson libraries to Android project
			final String JACKSON_FILE_PATH = GlobalConstants.WINDOWS_D_DRIVE
					+ File.separator + GlobalConstants.AND_GEN_ROOT_FOLDER
					+ File.separator + GlobalConstants.JACKSON_LIBRARY_FOLDER;

			PDEUtil.addLibrariesToProject(new File(JACKSON_FILE_PATH));

			// Refresh project
			PDEUtil.refreshProject();

			// Remove generated annotations from pojos
			ICompilationUnit[] pojoClasses = PDEUtil
					.getPackageClasses(packageName);

			for (ICompilationUnit compilationUnit : pojoClasses) {
				String classSource = compilationUnit.getSource();
				classSource = classSource
						.replaceAll(
								"@Generated\\(\\\"com\\.googlecode\\.jsonschema2pojo\\\"\\)",
								"");
				classSource = classSource.replaceAll(
						"import javax\\.annotation\\.Generated;", "");

				String classNameWithoutType = compilationUnit.getElementName()
						.replaceAll("\\.java", "");
				String classPath = PDEUtil.getFullClassPath(PDEUtil.getSrcFolderPath(),
						packageName, classNameWithoutType);
				FileUtil.writeFileAsUTF8(classSource, classPath);
			}

		} catch (FileUtilException e) {
			throw new CodeGeneratorException(e);

		} catch (Json2PojoGeneratorException e) {
			throw new CodeGeneratorException(e);
		} catch (CoreException e) {
			throw new CodeGeneratorException(e);
		} finally {
			new File(tempFilePath).delete();
		}
	}

	private String generateExceptionClass() throws CodeGeneratorException {

		Map<String, Object> propertyMap = new HashMap<String, Object>();
		String exceptionPackageName = restDescriptor.getPackagePath()
				+ ".exceptions";
		propertyMap.put("packageName", exceptionPackageName);

		String webServiceExceptionClassName = restDescriptor
				.getWebServiceName() + "Exception";
		propertyMap.put("exceptionName", webServiceExceptionClassName);

		GeneratorTemplate restClientExceptionTemplate = getDescriptor()
				.getTemplates().get(EXCEPTION_TEMPLATE);

		VelocityTemplate exceptionClassTemplate = new VelocityTemplate();
		exceptionClassTemplate.setTemplatePath(restClientExceptionTemplate
				.getTemplatePath());
		exceptionClassTemplate.setProperties(propertyMap);

		PDEUtil.generateClass(PDEUtil.getSrcFolderPath(), exceptionPackageName,
				webServiceExceptionClassName, exceptionClassTemplate);

		String exceptionClassPath = PDEUtil.getClassPath(exceptionPackageName,
				webServiceExceptionClassName);

		return exceptionClassPath;
	}

	private void constructParametersAndURL(List<Parameter> parameterList,
			Map<String, Object> propertyMap) {
		if (parameterList != null&&parameterList.size()>0) {

			StringBuilder paramsStringBuilder = new StringBuilder();
			StringBuilder urlParamsStringBuilder = new StringBuilder();

			if (restDescriptor.getUrlFormat() == URLFormat.AND) {
				urlParamsStringBuilder.append("?");
			} else {
				urlParamsStringBuilder.append("/");
			}

			for (Parameter parameter : parameterList) {
				switch (parameter.getParameterType()) {
				case INTEGER:
					paramsStringBuilder.append("Integer ")
							.append(parameter.getName()).append(",");

					break;
				case FLOAT:
					paramsStringBuilder.append("Float ")
							.append(parameter.getName()).append(",");
					break;
				case DOUBLE:
					paramsStringBuilder.append("Double ")
							.append(parameter.getName()).append(",");
					break;
				case LONG:
					paramsStringBuilder.append("Long ")
							.append(parameter.getName()).append(",");
					break;
				case BOOLEAN:
					paramsStringBuilder.append("Boolean ")
							.append(parameter.getName()).append(",");
					break;
				case STRING:
					paramsStringBuilder.append("String ")
							.append(parameter.getName()).append(",");
					break;
				}

				if (restDescriptor.getUrlFormat() == URLFormat.AND) {
					urlParamsStringBuilder.append(parameter.getName())
							.append("=").append("\"+")
							.append(parameter.getName()).append("+\"");
					urlParamsStringBuilder.append("&");
				} else {
					urlParamsStringBuilder.append("\"+")
							.append(parameter.getName()).append("+\"");
					urlParamsStringBuilder.append("/");
				}
			}
			if (paramsStringBuilder.length() != 0
					&& paramsStringBuilder.length() != 0) {
				paramsStringBuilder
						.deleteCharAt(paramsStringBuilder.length() - 1);
				urlParamsStringBuilder.deleteCharAt(urlParamsStringBuilder
						.length() - 1);
			}
			propertyMap.put("parameters", paramsStringBuilder.toString());
			propertyMap.put("webServiceURL", restDescriptor.getWebServiceURL()
					+ urlParamsStringBuilder.toString());

		} else {
			propertyMap.put("parameters", "");
			propertyMap.put("webServiceURL", restDescriptor.getWebServiceURL());
		}
	}

	private void generateRESTWSClient() throws CodeGeneratorException {

		// Generate Exception Class
		exceptionClassPath = generateExceptionClass();

		// Generate RESTWSClient
		Map<String, Object> propertyMap = new HashMap<String, Object>();
		String restWsPackageName = restDescriptor.getPackagePath() + ".ws";

		propertyMap.put("jsonPojoName", pojoClassPath);
		propertyMap.put("jsonPojoClass", pojoClassPath + ".class");

		propertyMap.put("exceptionClassName", exceptionClassPath);
		propertyMap.put("packageName", restWsPackageName);

		// Construct Parameter and URL in Velocity Template
		constructParametersAndURL(restDescriptor.getParameters(), propertyMap);

		String webServiceClassName = restDescriptor.getWebServiceName()
				+ "RESTClient";
		propertyMap.put("restWSClientName", webServiceClassName);
		propertyMap.put("operationType", restDescriptor.getType());
		propertyMap.put("operationName", restDescriptor.getOperationName());

		GeneratorTemplate restClientTemplate = getDescriptor().getTemplates()
				.get(REST_WS_GET_CLIENT_TEMPLATE);

		VelocityTemplate restClientClassTemplate = new VelocityTemplate();
		restClientClassTemplate.setTemplatePath(restClientTemplate
				.getTemplatePath());
		restClientClassTemplate.setProperties(propertyMap);

		PDEUtil.generateClass(PDEUtil.getSrcFolderPath(), restWsPackageName,
				webServiceClassName, restClientClassTemplate);

		restWSClientClassPath = PDEUtil.getClassPath(restWsPackageName,
				webServiceClassName);

	}

	private void constructParamatersAsMessages(List<Parameter> parameterList,
			Map<String, Object> propertyMap) {

		if (parameterList != null) {

			StringBuilder paramsStringBuilder = new StringBuilder();

			for (Parameter parameter : parameterList) {

				paramsStringBuilder.append(parameter.getName()).append(",");

			}
			if (paramsStringBuilder.length() != 0) {
				paramsStringBuilder
						.deleteCharAt(paramsStringBuilder.length() - 1);
			}
			propertyMap.put("restClientOperation", restWSClientClassPath + "."
					+ restDescriptor.getOperationName() + "("
					+ paramsStringBuilder.toString() + ")");

		}
	}

	private String constructParamatersDefaultValuesAsMessages(
			List<Parameter> parameterList) {

		StringBuilder paramsStringBuilder = new StringBuilder();

		if (parameterList != null) {

			for (Parameter parameter : parameterList) {

				String parameterDefaultValue = parameter.getParameterType() == ParameterType.STRING ? "\""
						+ parameter.getDefaultValue() + "\""
						: parameter.getDefaultValue();

				paramsStringBuilder.append(parameterDefaultValue).append(",");

			}
			paramsStringBuilder.deleteCharAt(paramsStringBuilder.length() - 1);

		}

		return paramsStringBuilder.toString();
	}

	private void generateServiceClass() throws CodeGeneratorException {
		// Generate ServiceClass
		Map<String, Object> propertyMap = new HashMap<String, Object>();
		String servicePackageName = restDescriptor.getPackagePath()
				+ ".service";

		propertyMap.put("jsonPojoName", pojoClassPath);

		propertyMap.put("exceptionClassName", exceptionClassPath);
		propertyMap.put("packageName", servicePackageName);
		propertyMap.put("operationName", restDescriptor.getOperationName());

		// Construct Parameter and URL in Velocity Template
		constructParametersAndURL(restDescriptor.getParameters(), propertyMap);
		constructParamatersAsMessages(restDescriptor.getParameters(),
				propertyMap);

		String serviceClassName = restDescriptor.getWebServiceName()
				+ "Service";
		propertyMap.put("serviceName", serviceClassName);

		GeneratorTemplate serviceTemplate = getDescriptor().getTemplates().get(
				SERVICE_TEMPLATE);

		VelocityTemplate restClientClassTemplate = new VelocityTemplate();
		restClientClassTemplate.setTemplatePath(serviceTemplate
				.getTemplatePath());
		restClientClassTemplate.setProperties(propertyMap);

		PDEUtil.generateClass(PDEUtil.getSrcFolderPath(), servicePackageName,
				serviceClassName, restClientClassTemplate);

		serviceClassPath = PDEUtil.getClassPath(servicePackageName, serviceClassName);
	}

	private void generateActivityFiles() throws CodeGeneratorException {
		// Generate Activity Files
		Map<String, Object> propertyMap = new HashMap<String, Object>();
		String activityPackageName = restDescriptor.getPackagePath()
				+ ".activity";

		propertyMap.put("jsonPojoName", pojoClassPath);
		propertyMap.put("packageName", activityPackageName);
		propertyMap.put("exceptionName", exceptionClassPath);

		String activityClassName = restDescriptor.getWebServiceName()
				+ "Activity";
		propertyMap.put("activityName", activityClassName);
		propertyMap.put("activityNameThis", activityClassName + ".this");

		propertyMap.put("activityFullPath", activityPackageName + "."
				+ activityClassName);

		String activityAdapterClassName = restDescriptor.getWebServiceName()
				+ "ActivityAdapter";
		propertyMap.put("activityAdapterName", activityAdapterClassName);

		propertyMap
				.put("serviceOperation",
						serviceClassPath
								+ ".getInstance()."
								+ restDescriptor.getOperationName()
								+ "("
								+ constructParamatersDefaultValuesAsMessages(restDescriptor
										.getParameters()) + ")");

		Map<String, Mapper> mappers = restDescriptor.getMappers();
		Mapper listMapper = mappers.get(GlobalConstants.LIST_MAPPER);
		Map<String, String> velocityParams = listMapper.buildAndGetMapper();

		propertyMap.putAll(velocityParams);

		try {

			propertyMap.put("RClassLocation",
					PDEUtil.findClassPackage("R", ".java"));

			GeneratorTemplate activityTemplate = getDescriptor().getTemplates()
					.get(ACTIVITY_TEMPLATE);

			VelocityTemplate activityFilesTemplate = new VelocityTemplate();
			activityFilesTemplate.setTemplatePath(activityTemplate
					.getTemplatePath());
			activityFilesTemplate.setProperties(propertyMap);

			String fileContent = VelocityUtil
					.buildTemplate(activityFilesTemplate);

			String[] fileContents = fileContent.split("//SplitHere");

			System.out.println("No. of files=" + fileContents.length);

			// Generate Activity Class
			String activityClassContent = fileContents[0];

			PDEUtil.generateClass(PDEUtil.getSrcFolderPath(), activityPackageName,
					activityClassName, activityClassContent);

			// Generate Adapter Class
			String adapterClassContent = fileContents[1];

			PDEUtil.generateClass(PDEUtil.getSrcFolderPath(), activityPackageName,
					activityClassName + "Adapter", adapterClassContent);

			// Generate all XMLs
			String listMainXMLContent = fileContents[2];
			// To remove \n\r
			listMainXMLContent = listMainXMLContent.substring(2);

			AndroidUtil.generateAndroidFile(AndroidUtil.getAndroidResourceFolderPath(),
					GlobalConstants.LAYOUT_FOLDER_NAME, "list_main.xml",
					listMainXMLContent);

			String rowLayoutXMLContent = fileContents[3];
			// To remove \n\r
			rowLayoutXMLContent = rowLayoutXMLContent.substring(2);

			AndroidUtil.generateAndroidFile(AndroidUtil.getAndroidResourceFolderPath(),
					GlobalConstants.LAYOUT_FOLDER_NAME,
					"row_layout_english.xml", rowLayoutXMLContent);

			String listSelectorXMLContent = fileContents[4];
			// To remove \n\r
			listSelectorXMLContent = listSelectorXMLContent.substring(2);

			AndroidUtil.generateAndroidFile(AndroidUtil.getAndroidResourceFolderPath(),
					GlobalConstants.DRAWABLE_FOLDER_NAME,
					"list_selector_english.xml", listSelectorXMLContent);

			// Inject Internet Permission to Android Manifest
			AndroidManifestUtil androidManifest = new AndroidManifestUtil();
			androidManifest
					.insertPermission(AndroidManifestUtil.INTERNET_PERMISSION);

			// Inject Activity to Android Manifest
			String activityFullClassPath = activityPackageName + "."
					+ activityClassName;
			androidManifest.insertActivity(activityFullClassPath);

			androidManifest.commitChanges();

			// Move images to their folders
			final String IMAGES_FOLDER_PATH = GlobalConstants.WINDOWS_D_DRIVE
					+ File.separator + GlobalConstants.AND_GEN_ROOT_FOLDER
					+ File.separator + GlobalConstants.IMAGES_FOLDER;

			File imagesFolder = new File(IMAGES_FOLDER_PATH);

			if (imagesFolder.isDirectory()) {
				for (File image : imagesFolder.listFiles()) {
					String destinationPath = AndroidUtil
							.getAndroidResourceFolderPath()
							+ GlobalConstants.DRAWABLE_HDPI_FOLDER_NAME
							+ File.separator + image.getName();

					FileUtil.copyFile(image.getAbsolutePath(), destinationPath);
				}
			}

		} catch (Exception e) {
			throw new CodeGeneratorException(e);
		}

	}

	@Override
	public void generateIntegrationAndServiceCode(Object descriptor)
			throws CodeGeneratorException {
		this.restDescriptor = (RESTWebServiceDescriptor) descriptor;

		// Generate JSON Pojos
		generateJsonPojos();

		// Generate REST web service client
		generateRESTWSClient();

		// Generate Service Class
		generateServiceClass();

		setIntegrationAndServiceCodeGenerated(true);

	}

	@Override
	public void generatePresentationCode() throws CodeGeneratorException {

		if (!isIntegrationAndServiceCodeGenerated()) {
			throw new CodeGeneratorException(
					"generateIntegrationAndServiceCode() must be run first");
		}

		// Generate Activity
		generateActivityFiles();

	}

	@Override
	public void finish() throws CodeGeneratorException {
		try {
			PDEUtil.refreshProject();
		} catch (CoreException e) {
			throw new CodeGeneratorException(e);
		}
	}

}
